package com.zhou.hot.agent;


import com.zhou.hot.util.HotResult;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.lang.instrument.ClassDefinition;
import java.lang.instrument.Instrumentation;
import java.nio.charset.Charset;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Properties;

@Slf4j
public class HotLoadAgent
{
  static HotLoadConfig CONFIG;

  public static SimpleDateFormat fmt = new SimpleDateFormat("yyyy_MM_dd_HH_mm_ss");
    /**
     * 实现重定义Class配置
     * @param args
     * @param inst
     */
  public static void agentmain(String args, Instrumentation inst) {
    try {
      initConfig();
    } catch (Exception e) {
      log.error("CONFIG LOAD出错",e);
      return;
    }
      log.info("[HotLoadAgent]  启动....classesDir={},\n classesConfig={}",CONFIG.getClassesDir(),CONFIG.getClassesConfig());
    File dir = new File(CONFIG.getClassesDir());
    Properties properties = CONFIG.loadClassesConfigProperties();
  //LOAD
   HotResult hotResult = loadClassDefinitions(dir,properties);
    if (hotResult.isFail()){
        return;
    }

    List<ClassDefinition> definitions = hotResult.getDefinitions();
    List<File> classFiles = hotResult.getFiles();
    //重定义class
    hotResult = redefineClasses(inst,definitions);
    //报错到历史文件中
    if (hotResult.isSucc()) {
      moveToHistory(classFiles);
    }
      log.info("[HotLoadAgent]  更新结束...");

  }

  public static void moveToHistory(List<File> classFiles) {
      String path = CONFIG.getClassesHistory()+"/"+fmt.format(new Date());
      try {
          log.info("[HotLoadAgent]   移动至history["+path+"]");
          File historyDir = new File(path);
          if (!historyDir.exists()) {
              historyDir.mkdirs();
          }
          for (File clazzFile : classFiles) {
            boolean moveSucc = clazzFile.renameTo(new File(historyDir.getAbsolutePath(), clazzFile.getName()));
            if (!moveSucc) {
                log.info("[HotLoadAgent]  " + clazzFile.getName() + "移动至history目录失败,请手动移动!");
            }
          }
      } catch (Exception e) {
          log.error("[HotLoadAgent]   移动至history["+path+"]目录失败!",e);
      }
  }

  private static HotResult redefineClasses(Instrumentation inst, List<ClassDefinition> definitions) {
    try {
      ClassDefinition[] arr = new ClassDefinition[definitions.size()];
      definitions.toArray(arr);
      inst.redefineClasses(arr);
      /*System.out.printf("已替换%d个class...\n", new Object[] { Integer.valueOf(arr.length) });*/
        log.info("[HotLoadAgent]  " + "已替换" + arr.length + "个class文件...");
      return HotResult.RESULT_SUCC;
    } catch (Exception e) {
        log.error("[HotLoadAgent]  替换失败", e);
    }
    return HotResult.RESULT_FAIL;
  }

  private static HotResult loadClassDefinitions(File dir, Properties properties) {
      if (dir == null || !dir.exists()) {
          log.error("[HotLoadAgent]  parent path : " + dir.getPath()+" not exist.",new Exception());
          return HotResult.createFailResult("classes文件夹内不存在");
      }
      File[] listFiles = dir.listFiles();
      if (listFiles == null || listFiles.length <= 0) {
          log.error("[HotLoadAgent]  parent path(" +  dir.getPath()+") sub class files size = 0.",new Exception());
          return HotResult.createFailResult("class文件数量为空");
      }
    List<ClassDefinition> definitions = new ArrayList<>();
    List<File> classFiles = new ArrayList<>();
    int classFileSize = 0;
    for (File clazzFile : listFiles) {
      if (!clazzFile.getName().endsWith(".class")) {
          continue;
      }
        classFileSize ++;
      String className = checkClassFilePackage(clazzFile,properties);
      if (className == null) {
        continue;
      }
      Class clazz = null;
      try {
        clazz = Class.forName(className);
      } catch (ClassNotFoundException e) {
        clazz = null;
        //这里使用热加载在意义不大，替换class字节码，不会创建对象
          /*try {
              clazz = HotClassloaderHelp.loadClass(dir,className);
          } catch (Exception e1) {
              logger.error("[HotLoadAgent] 2 " + clazzFile.getName() + " 配置出错,value:" + className,e);
          }*/
          log.error("[HotLoadAgent]  " + clazzFile.getName() + " 配置出错,value:" + className,e);
      }
      if (clazz == null) {
//        System.out.printf("%s配置出错,value:%s\n", new Object[] { clazzFile.getName(), className });
        log.info("[HotLoadAgent]  fail " + clazzFile.getName() + " 配置出错,value:" + className);
        return HotResult.RESULT_FAIL;
      }
      byte[] bytes = fileToBytes(clazzFile);
      if (bytes == null) {
        log.error(clazzFile.getName() + "文件出错");
        return HotResult.RESULT_FAIL;
      }
//      System.out.printf("%s文件大小:%d\n", new Object[] { clazzFile.getName(), Integer.valueOf(bytes.length) });
      log.info("[HotLoadAgent]  " + clazzFile.getName() + "文件大小:" + bytes.length);
      ClassDefinition classDefinition = new ClassDefinition(clazz, bytes);
      definitions.add(classDefinition);
      classFiles.add(clazzFile);
    }

    return HotResult.createSuccResult(definitions,classFiles);
  }

    /**
     * 加载配置
     * @throws Exception
     */

  public static void initConfig() throws Exception {
    CONFIG = new HotLoadConfig();
    CONFIG.load();
  }


  public static byte[] fileToBytes(File file) {
      FileInputStream in = null;
      ByteArrayOutputStream baos = null;
      try {
      in = new FileInputStream(file);
      baos = new ByteArrayOutputStream();
      byte[] buffer = new byte[1024];
      int readSize = -1;
      while ((readSize = in.read(buffer)) != -1) {
        baos.write(buffer, 0, readSize);
      }

      baos.flush();

      return baos.toByteArray();
    } catch (IOException e) {
      log.error("[HotLoadAgent]  文件:" + file.getName() + "读取出错",e);
        return null;
    }finally {
          try {
              if (in != null) {
                  in.close();
              }
              if (baos != null ) {
                  baos.close();
              }
          } catch (IOException e) {
          }
      }

  }

  public static String checkClassFilePackage(File file,Properties properties)
  {
      String packageClassName = null;
      if (properties != null) {
          packageClassName = properties.getProperty(file.getName());
      }
      if (packageClassName == null)
      {
          packageClassName = javapClassFile(file);
      }
      log.info("[HotLoadAgent]  packageClassName:"+packageClassName );
      return packageClassName;
  }

    /**
     * 通过javam命令直接从class文件中获取全包类
     * @param file
     * @return
     */
  public static String javapClassFile(File file)
  {
      String packageName = null;
      BufferedReader bufferedReader = null;
      Process process = null;
      try {

          String name = file.getName().substring(0,file.getName().lastIndexOf("."));
          Runtime run = Runtime.getRuntime();
          String JDK = System.getenv("JAVA_HOME");

          String jdkBin = JDK+"/bin/";

          //可打开exe程序，也可执行cmd命令，注意 路径\要使用\\，表示转义
          // Process p = run.exec("E:\\一卡通测试工具\\串口调试助手\\kComm.exe");  //执行exe程序
          String cmd = jdkBin+"javap "+file.getAbsolutePath();
          process = run.exec(cmd);                  //执行CMD命令
          bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.forName("UTF-8")));
          log.info("[HotLoadAgent]  javapClassFile cmd :"+ cmd);
          List<String> list = new ArrayList<>();
          String lineMes;
          int size =0;
          while ((lineMes = bufferedReader.readLine()) != null)
          {
//              System.out.println(lineMes);// 打印输出信息
              size ++;
              if (size == 2)
              {
                  String[] array = lineMes.trim().split(" ");
                  for (int i=0;i<array.length;i++)
                  {
                      if (array[i].equalsIgnoreCase("interface") || array[i].equalsIgnoreCase("class") || array[i].equalsIgnoreCase("enum"))
                      {
                          String packageClassName = array[i+1];
                          if (packageClassName.endsWith(name))
                          {
                              packageName = packageClassName;
//                              System.err.println(packageClassName);
                              log.info("[HotLoadAgent]  读取 class文件["+file.getName()+"] 获取包全名类："+packageClassName);
                              break;
                          } else if (packageClassName.contains(name)){
                          int lastIndexOf = packageClassName.lastIndexOf(name);
                          if (lastIndexOf <=0) {
                              packageName = name;
                              break;
                          }
                          packageName = packageClassName.substring(0,lastIndexOf)+name;
                          break;
                      }

                      }
                  }
                  break;
              }
          }
          //检查命令是否执行失败。
          if (process.waitFor() != 0) {
              if (process.exitValue() == 1){
                  //0表示正常结束，1：非正常结束
                  log.error("[HotLoadAgent]  命令执行失败,exitValue="+process.exitValue());
                  throw new Exception("命令执行失败");
              }
          }

      } catch (Exception e) {
          log.error("",e);
      }finally {
          try {
              if(bufferedReader != null) {
                  bufferedReader.close();
              }
          } catch (IOException e) {
          }
      }
      return packageName;
  }
  
}